#####################################################################
# File:         Library.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Brian Oliver
#
# Description:  This file defines the Library class, the container for
#               all Component instances for in a Library
#
# See Also:     GenericObject, GenericContainer, 
#               Component, ComponentContainer, LibraryItem
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.KnowledgeEngineCore import *
from Products.KnowledgeEngine.IndexedGenericContainer import IndexedGenericContainer, IndexedGenericContainerInfo
from Products.KnowledgeEngine.Environment.Environment import Environment
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from Globals import Persistent
from ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## LibraryInfo Class Definition
class LibraryInfo(IndexedGenericContainerInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'Library'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Library"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "library"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('www/icon.gif', globals())
       
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """
        
        #DEFAULT RULE: Libraries are only permitted in Environments or Folders
        return containerClassInfo.isInstanceOf('Environment') or containerClassInfo.isInstanceOf('Folder')

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: Libraries may only contain Components BUT NOT LibraryItems
        return childClassInfo.isInstanceOf('Component') and not childClassInfo.isInstanceOf('LibraryItem')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        IndexedGenericContainerInfo.onConfigureProperties(self)
        
        #add the 'author' property        
        self.addPropertyDefinition(PropertyDefinition(identity="author", displayName="Author", description="The name of the Library author", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_SINGLELINE, propertyStructure=ps_ATOMIC))        
        
        #add the 'description' property        
        self.addPropertyDefinition(PropertyDefinition(identity="description", displayName="Description", description="The description of the library", defaultValue="", propertyType=pt_STRING, propertyFormat=pf_TEXT_MULTILINE, propertyStructure=ps_ATOMIC))        
    
    #---------------------------------------------------------------#

#####################################################################
## Library Class Definition
class Library(IndexedGenericContainer):
    """
        Base container for all Library Components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = LibraryInfo()
 
    #---------------------------------------------------------------#
    # DTML Methods
    # (none)
    
    #---------------------------------------------------------------#
    # Default Attribute Values

    #a dictionary of volatile references to components with in the Library
    #indexed by component Identity
    _v_Components = {}
    
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the object"        
        
        IndexedGenericContainer.__init__(self, id, identity)
        
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """
        #set the volatile attributes
        self._v_Components = {}

        #perform default onAfterInstanciation for the container
        IndexedGenericContainer.onAfterInstanciation(self)
        
    #---------------------------------------------------------------#
        
    def getDescription(self):
        "Returns the Description of the model"
        return self.description        

    #---------------------------------------------------------------#
        
    def setDescription(self, description):
        "Sets the Description of the model"
        self.description = description

    #---------------------------------------------------------------#
        
    def getAuthor(self):
        "Returns the Author of the model"
        return self.author  

    #---------------------------------------------------------------#
        
    def setAuthor(self, author):
        "Sets the Author of the model"
        self.description = description

    #---------------------------------------------------------------#

    def getEnvironment(self):
        "Return the KnowledgeEngine Environment in which this Library is located"
        
        lParent = self.getParent()
        
        while not isinstance(lParent, Environment):
            lParent = lParent.getParent()
        
        return lParent

    #---------------------------------------------------------------#
    
    def rebuildComponentsDictionary(self):
        """
            Attempt to rebuild the components dictionary, filling
            it with the components currently in the Library.
            
            Note: the component dictionary is volitile, so we have
            to rebuild it when the model is loaded/imported/copied.
        """
        
        ###print "DEBUG: Library.rebuildComponentsDictionary: Commenced"

        self._v_Components.clear()
        self._v_Components.update(self.getDescendants())

        ###print "DEBUG: Library.rebuildComponentsDictionary: Completed for %d Components" % len(self._v_Components)
    
    #---------------------------------------------------------------#

    def onAfterAddComponent(self, component):
        """
            Handle when a component has beed added somewhere in the Library. 
            Called by Component.onAfterAdd.
        """
        
        ###print "DEBUG: Library.onAfterAddComponent: Commenced"
        
        #does the dictionary have any components?
        if len(self._v_Components) == 0:
            self.rebuildComponentsDictionary()
            
        #add the component
        #(don't override previous entries with the same identity
        # so Cloning/Cut/Copy/Paste doesn't destroy existing entries)
        if not self._v_Components.has_key(component.getIdentity()):
            self._v_Components[component.getIdentity()] = component

        ###print "DEBUG: Library.onAfterAddComponent: Completed (%d)" % len(self._v_Components)

    #---------------------------------------------------------------#

    def onBeforeDeleteComponent(self, component):
        """
            Handle when a component is about to be deleted from 
            somewhere with in the Library. 
            Called by Component.onBeforeDelete.
        """

        ###print "DEBUG: Library.onBeforeDeleteComponent: Commenced"

        #remove the specified component
        if self._v_Components.has_key(component.getIdentity()):
            del self._v_Components[component.getIdentity()]

        ###print "DEBUG: Library.onBeforeDeleteComponent: Completed (%d)" % len(self._v_Components)

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        """
            Handle Knowledge Engine specific actions after this library
            has been added to an environment.
        """

        #perform normal onAfterAdd processing
        IndexedGenericContainer.onAfterAdd(self)

        #notify the environment that the library has been added
        try:
            self.getEnvironment().onAfterAddLibrary(self)
        except:
            pass
        
        ###print "DEBUG: Library.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the 
            identity of this object is changed.
            
            Called By GenericObject.onAfterClone
        """

        #perform normal onBeforeIdentityChange processing
        IndexedGenericContainer.onBeforeIdentityChange(self)

        try:
            ## The following code prevents duplicate Library cache entries 
            ## being created in an Environment when a Library is 
            ## copied between Environments.  That is, one Library
            ## with two different Identities in the target Environment.
            ##
            ## The problem is caused by the Zope platform performing a copy,
            ## then an add before the clone callback is called.  
            ## The process is as follows; 
            ## 1. copy is created (without changing Identities... no problem)
            ## 2. copy is added into the target environment, which is different
            ##    from the environment being copied from.  Because the Library
            ##    doesn't exist in the new environment, the Library is
            ##    added to the cache, with the original identity!
            ## 3. the onAfterClone callback is performed, which allocates 
            ##    new identities to the copies.  The Library is then added to 
            ##    the target environment cache (again), but this time with a 
            ##    new identity... there you go, one Library, two entries in an 
            ##    environment cache.
            
            #get the environment for this library
            environment = self.getEnvironment()
            
            #get the library with this library's identity from the environment
            library = environment.getLibrary(self.getIdentity())
            
            #is the library with this library's identity, this library?
            #(if it is, this library is already in the environment, so remove it)
            if self == library:
                #remove the library, as it will be added with a new identity
                #by the Library.onAfterClone
                environment.onBeforeDeleteLibrary(self)

        except:
            pass        
    
    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = {}):
        "Handle Knowledge Engine specific actions after this object has been cloned"

        #perform normal onAfterClone processing
        IndexedGenericContainer.onAfterClone(self, identityMappings)
        
        #clear the internal components dictionary (force reindexing)
        self._v_Components = {}
        
        #notify the environment that the library has been cloned (added)
        try:
            self.getEnvironment().onAfterAddLibrary(self)
        except:
            pass
        
        ###print "DEBUG: Library.onAfterClone: Completed"        

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        """
            Handle Knowledge Engine specific actions before this library is deleted
        """

        #TODO: remove component assessment data? (is this method called on a cut?)

        #perform normal onBeforeDelete processing
        IndexedGenericContainer.onBeforeDelete(self)

        #notify the environment that the library is about to be deleted
        try:
            self.getEnvironment().onBeforeDeleteLibrary(self)
        except:
            pass
        
        ###print "DEBUG: Library.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def containsComponent(self, identity):
        """
            Returns if the component with the specified identity
            is in the library component dictionary
        """
        
        #does the dictionary have any components?
        if len(self._v_Components) == 0:
            self.rebuildComponentsDictionary()

        return self._v_Components.has_key(identity)   

    #---------------------------------------------------------------#

    def getComponent(self, identity):
        """
            Return the component with the specified identity
            that exists within the Library.  Returns None if a
            component with the specified identity does not exist.
        """
        
        #does the dictionary have any components?
        if len(self._v_Components) == 0:
            self.rebuildComponentsDictionary()

        ###print "DEBUG: Library.getComponent: Get Component %s from %s" % (identity,repr(self._v_Components))

        if self._v_Components.has_key(identity):
            return self._v_Components[identity]
        else:
            return None
    
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation

# Register Class Information with the Knowledge Engine
registerGenericObjectClass(Library)

#####################################################################
